package se.thoka.twt.client.utils;

import java.io.Serializable;
import java.util.Date;

import com.google.gwt.user.client.rpc.IsSerializable;

/**
 * @author Thobias Karlsson, Thoka
 * @since 9 jan 2010
 */
@SuppressWarnings("deprecation")
public class GWTTime implements IsSerializable, Serializable, SerializableType<String> {
	private static final long serialVersionUID = 8104539704015437941L;
	/**
	 * The hour of day (0..23).
	 */
	private int hourOfDay;
	/**
	 * The minute of hour (0..59).
	 */
	private int minuteOfHour;
	/**
	 * The seconds of minute (0..59).
	 */
	private int secondOfMinute;
	/**
	 * The millis of second (0..999).
	 */
	private int millisOfSecond;

	/**
	 * Creates a new instance of <code>GWTTime</code>.
	 */
	public GWTTime() {
		this(0, 0, 0, 0);
	}

	/**
	 * Creates a new instance of <code>GWTTime</code>.
	 * 
	 * @param time
	 *            The time of day. Must not be <code>null</code>.
	 */
	public GWTTime(Date time) {
		this(time.getHours(), time.getMinutes(), time.getSeconds(), 0);
	}

	/**
	 * Creates a new instance of <code>GWTTime</code>.
	 * 
	 * @param hourOfDay
	 *            The hour of day.
	 * @param minuteOfHour
	 *            The minutes of hour.
	 */
	public GWTTime(int hourOfDay, int minuteOfHour) {
		this(hourOfDay, minuteOfHour, 0, 0);
	}

	/**
	 * Creates a new instance of <code>GWTTime</code>.
	 * 
	 * @param hourOfDay
	 *            The hour of day.
	 * @param minuteOfHour
	 *            The minutes of hour.
	 * @param secondOfMinute
	 *            The seconds of minute.
	 * @param millisOfSecond
	 *            The milliseconds of second.
	 */
	public GWTTime(int hourOfDay, int minuteOfHour, int secondOfMinute,
			int millisOfSecond) {
		super();
		setHourOfDay(hourOfDay);
		setMinuteOfHour(minuteOfHour);
		setSecondOfMinute(secondOfMinute);
		setMillisOfSecond(millisOfSecond);
	}

	/**
	 * Creates a new instance of <code>GWTTime</code>.
	 * 
	 * @param gwtTime
	 *            The <code>GWTTime</code> to copy.
	 */
	public GWTTime(GWTTime gwtTime) {
		this(gwtTime.getHourOfDay(), gwtTime.getMinuteOfHour(), gwtTime
				.getSecondOfMinute(), gwtTime.getMillisOfSecond());
	}

	/**
	 * Returns the hour of day (24 hour clock).
	 * 
	 * @return Returns the hour of day (a value between 0 and 23).
	 */
	public int getHourOfDay() {
		return this.hourOfDay;
	}

	/**
	 * Returns the milliseconds of second.
	 * 
	 * @return Returns the milliseconds (a value between 0 and 999).
	 */
	public int getMillisOfSecond() {
		return this.millisOfSecond;
	}

	/**
	 * Returns the minutes of hour.
	 * 
	 * @return Returns the minutes (a value between 0 and 59).
	 */
	public int getMinuteOfHour() {
		return this.minuteOfHour;
	}

	/**
	 * Returns the seconds of minute.
	 * 
	 * @return Returns the seconds (a value between 0 and 59).
	 */
	public int getSecondOfMinute() {
		return this.secondOfMinute;
	}

	/**
	 * Sets the hour of day (24 hour clock).
	 * 
	 * @param hourOfDay
	 *            The new hour of day.
	 */
	public void setHourOfDay(int hourOfDay) {
		this.hourOfDay = hourOfDay;
	}

	/**
	 * Sets the milliseconds of second.
	 * 
	 * @param millisOfSecond
	 *            The new milliseconds (a value between 0 and 999).
	 */
	public void setMillisOfSecond(int millisOfSecond) {
		this.millisOfSecond = millisOfSecond;
	}

	/**
	 * Sets the minutes of hour.
	 * 
	 * @param minuteOfHour
	 *            The new minutes (a value between 0 and 59).
	 */
	public void setMinuteOfHour(int minuteOfHour) {
		this.minuteOfHour = minuteOfHour;
	}

	/**
	 * Sets the seconds of minute.
	 * 
	 * @param secondOfMinute
	 *            The new seconds (a value between 0 and 59).
	 */
	public void setSecondOfMinute(int secondOfMinute) {
		this.secondOfMinute = secondOfMinute;
	}

	@Override
	public String toString() {
		return writeSerial();
	}

	/**
	 * Converts this <code>GWTTime</code> into a {@link GWTDateTime} at the
	 * supplied {@link GWTDate}.
	 * 
	 * @param date
	 *            The {@link GWTDate}. Must not be <code>null</code>.
	 * @return A new {@link GWTDateTime}.
	 */
	public GWTDateTime toDateTime(GWTDate date) {
		return new GWTDateTime(new GWTDate(date), new GWTTime(this));
	}

	@Override
	public boolean equals(Object obj) {
		return (obj != null && obj.hashCode() == hashCode());
	}

	@Override
	public int hashCode() {
		return 157 + 11 * getHourOfDay() + 17 * getMinuteOfHour() + 19
				* getSecondOfMinute() + 23 * getMillisOfSecond();
	}

	/**
	 * Compares this <code>GWTTime</code> to another, returning true if this
	 * time is strictly after the other time.
	 * 
	 * @param time
	 *            The time of day to compare against.
	 * @return <code>true</code> if this time is after the <code>time</code>
	 *         parameter.
	 */
	public boolean isAfter(GWTTime time) {
		boolean result = false;
		if (!equals(time)) {
			// If we're not equal we are the inverse of isBefore.
			result = !isBefore(time);
		}
		return result;
	}

	/**
	 * Compares this <code>GWTTime</code> to another, returning true if this
	 * time is strictly before the other time.
	 * 
	 * @param time
	 *            The time of day to compare against.
	 * @return <code>true</code> if this time is before the <code>time</code>
	 *         parameter.
	 */
	public boolean isBefore(GWTTime time) {
		boolean result = false;
		if (!equals(time)) {
			result = getHourOfDay() < time.getHourOfDay();
			if (!result && getHourOfDay() == time.getHourOfDay()) {
				result = getMinuteOfHour() < time.getMinuteOfHour();
				if (!result && getMinuteOfHour() == time.getMinuteOfHour()) {
					result = getSecondOfMinute() < time.getSecondOfMinute();
					if (!result
							&& getSecondOfMinute() == time.getSecondOfMinute()) {
						result = getMillisOfSecond() < time.getMillisOfSecond();
					}
				}
			}
		}
		return result;
	}
	
	@Override
	public void readSerial(String serial) throws IllegalArgumentException, NullPointerException {
		String[] millisSplit = serial.split("\\.");
		if (millisSplit.length != 2) {
			throw new IllegalArgumentException("Illegal time format for " + serial + ", missing '.' for millis (HH:MM:SS.mmm)");
		}
		String[] split = millisSplit[0].split(":");
		if (split.length != 3) {
			throw new IllegalArgumentException("Illegal time format for " + serial + " (HH:MM:SS.mmm)");
		}
		setHourOfDay(ParseUtil.tryParse(split[0], 0));
		setMinuteOfHour(ParseUtil.tryParse(split[1], 0));
		setSecondOfMinute(ParseUtil.tryParse(split[2], 0));
		setMillisOfSecond(ParseUtil.tryParse(millisSplit[1], 0));
	}
	
	@Override
	public String writeSerial() {
		StringBuilder sb = new StringBuilder();
		sb.append(StringUtils.zeroPad(getHourOfDay(), 2));
		sb.append(':');
		sb.append(StringUtils.zeroPad(getMinuteOfHour(), 2));
		sb.append(':');
		sb.append(StringUtils.zeroPad(getSecondOfMinute(), 2));
		sb.append('.');
		sb.append(StringUtils.zeroPad(getMillisOfSecond(), 3));

		return sb.toString();
	}
}
